using System;

namespace GameOfLifeX.Core
{
	public class GameOfLifeModel
	{	
		private byte[,] _grid;
		private readonly int _gridWidth;
		private readonly int _gridHeight;
		readonly Random _random = new Random (DateTime.Now.Millisecond);

		public GameOfLifeModel (int gridWidth, int gridHeight)
		{
			_gridWidth = gridWidth;
			_gridHeight = gridHeight;
			_grid = new byte[gridWidth, gridHeight];
		}

		public void SeedRandomly ()
		{
			for (int i = 0; i < _gridWidth; i++)
				for (int j = 0; j < _gridHeight; j++)
					if (_random.Next (15) == 1)
						SetGridValue (i, j, true, _grid, _grid);

		}

		public void Clear ()
		{
			_grid = new byte[_gridWidth, _gridHeight];
		}

		//1.Any live cell with fewer than two live neighbours dies, as if caused by under-population.
		//2.Any live cell with two or three live neighbours lives on to the next generation.
		//3.Any live cell with more than three live neighbours dies, as if by overcrowding.
		//4.Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
		public void RunGeneration ()
		{

			var tempGrid = (byte[,])_grid.Clone ();
					
			for (int x = 0; x < _gridWidth; x++)
				for (int y = 0; y < _gridHeight; y++)
				{
					bool alive = Convert.ToBoolean ( _grid[x,y] & 0x03);
					byte neightbourCount = (byte)(_grid [x, y] >> 4);

					//1.Any live cell with fewer than two live neighbours dies, as if caused by under-population.
					if (alive && neightbourCount < 2)
						SetGridValue (x, y, false, _grid, tempGrid);

					//3.Any live cell with more than three live neighbours dies, as if by overcrowding.
					else if (alive && neightbourCount > 3)
						SetGridValue (x, y, false, _grid, tempGrid);
					else if ((!alive) && neightbourCount == 3)
						SetGridValue (x, y, true, _grid, tempGrid);

				}

			_grid = tempGrid;				
		}

		public void GenerateCellsAtTouchLocation (float width, float height, float x, float y)
		{
			var xFactor = _gridWidth / width;
			var yFactor = _gridHeight / height;

			int convertedX =(int) Math.Round ( x * xFactor);
			int convertedY =(int) Math.Round ( y * yFactor);

			SetGridValue (convertedX-1, convertedY, true, _grid, _grid);
			SetGridValue (convertedX, convertedY, true, _grid, _grid);
			SetGridValue (convertedX+1, convertedY, true, _grid, _grid);

			SetGridValue (convertedX-1, convertedY-1, true, _grid, _grid);
			SetGridValue (convertedX, convertedY-1, true, _grid, _grid);
			SetGridValue (convertedX+1, convertedY-1, true, _grid, _grid);

			SetGridValue (convertedX-1, convertedY+1, true, _grid, _grid);
			SetGridValue (convertedX, convertedY+1, true, _grid, _grid);
			SetGridValue (convertedX+1, convertedY+1, true, _grid, _grid);
		}

		private void SetGridValue (int x, int y, bool value, byte[,] sourceGrid, byte[,] targetGrid)
		{
			if (x >= _gridWidth)
				x = x - _gridWidth;

			if (x < 0)
				x = x + _gridWidth;

			if (y >= _gridHeight)
				y = y - _gridHeight;

			if (y < 0)
				y = y + _gridHeight;

			if (value)
			{
				targetGrid [x, y] += 1; // TODO hvad hvis den allerede er blevet tændt ? - så skal vi vel intet gøre ?

				IncrementNeighbourCount (x - 1, y - 1, targetGrid);  // north west
				IncrementNeighbourCount (x, y - 1, targetGrid);      // north
				IncrementNeighbourCount (x + 1, y - 1, targetGrid);  // north east
				IncrementNeighbourCount (x - 1, y, targetGrid);      // west  
				IncrementNeighbourCount (x + 1, y, targetGrid);      // east            
				IncrementNeighbourCount (x - 1, y + 1, targetGrid);  // south west            
				IncrementNeighbourCount (x, y + 1, targetGrid);      // south                                    
				IncrementNeighbourCount (x + 1, y + 1, targetGrid);  // south east 
			}
			else
			{
				targetGrid [x, y] -= 1; // TODO hvad hvis den allerede er blevet tændt ? - så skal vi vel intet gøre ?

				DecrementNeighbourCount (x - 1, y - 1, targetGrid);  // north west
				DecrementNeighbourCount (x, y - 1, targetGrid);      // north
				DecrementNeighbourCount (x + 1, y - 1, targetGrid);  // north east
				DecrementNeighbourCount (x - 1, y, targetGrid);      // west  
				DecrementNeighbourCount (x + 1, y, targetGrid);      // east            
				DecrementNeighbourCount (x - 1, y + 1, targetGrid);  // south west            
				DecrementNeighbourCount (x, y + 1, targetGrid);      // south                                    
				DecrementNeighbourCount (x + 1, y + 1, targetGrid);  // south east 
			}
		}

		private void IncrementNeighbourCount (int x, int y, byte[,] targetGrid)
		{
			if (x >= _gridWidth)
				x = x - _gridWidth;

			if (x < 0)
				x = x + _gridWidth;

			if (y >= _gridHeight)
				y = y - _gridHeight;

			if (y < 0)
				y = y + _gridHeight;

			targetGrid [x, y] += 1 << 4;
		}

		private void DecrementNeighbourCount (int x, int y, byte[,] targetGrid)
		{
			if (x >= _gridWidth)
				x = x - _gridWidth;

			if (x < 0)
				x = x + _gridWidth;

			if (y >= _gridHeight)
				y = y - _gridHeight;

			if (y < 0)
				y = y + _gridHeight;

			targetGrid [x, y] -= 1 << 4;
		}

		public byte[,] GetGrid ()
		{
			return _grid;
		}

		public int Height { get { return _gridHeight; } }

		public int Width { get { return _gridWidth; } }
	}
}
